import base64
import time

#Auther：wujehy
# email:wujehy@qq.com
# @File : Web.py 
# @Time : 19-4-2 下午8:58
from queue import Queue

from django.http import JsonResponse
from django.shortcuts import render_to_response
from django.views.decorators.csrf import csrf_exempt

from .APIUnit import APIUnit
from .Model import SensorData
import paho.mqtt.client as mqtt
"""
#   这个将献给 学校 2019 以后的物联网的同学 上课实训
#   现在只是同于 面试demo
#   python 作为 快构建模型 模型成立 则可以 按照 这样去 翻译成 javaee jsp or c++ 的cgi 

用于 页面的展示

实现 采集信息的处理 ,储存,以及发送信息的组装

使用的环境是:树莓派 配合 zigbee 协调器 连在一起 作为一个 网关
    这样配置是因为: 时间成本太低 时间只有两天就要完成所以 python + 树莓派 是 最佳的组合
        请求 前期 使用 get 方便测是 有时间 在 用post 这个 需要多一步 安全校验 在生产 环境 的时候 很实用

sensorlist 存的是 每个painid 下的设备信息
panid 仅仅是作为子网 划分 实际panid 代表的 是 那和 位置 更具 后台的数据库 来判断

    需要的主要方法 有 
    getData :
        将信息展示到页面 使用json 返回
        
    upload :
        将网关的信息进行上传
        上传的 信息是 特定的指令 原本是 bytes 的 被 封装后的 base 64 字符串 防止 在传输过程数据出错
        
    base64decode:
        将字符 转化成 二进制 输入输出都是 byte类型 所以 字符需套 encode 在传进来
    base64encode:
        将二进制 转化成 字符 输入输出都是 byte类型 所以 字符需套 encode 在传进来
        
    control:
        手动控制指令输入 
        发送指令给这个方法 就可以 下发指令
        
        这里直接将 数据 进行 存入 队列是 存在 bug 的 如果有人 恶意 发送 破坏指令 则 后果不堪设想 
        不过 在 现在这个场景 可以这么做 不过 要控制输入,不过 除了 定义的指令可以识别 其他额指令在 协调器 是 过滤的 所以
        问题不大 
        因为 指令 可能错误 所以 要加入 try
    status:
        状态查询的方法 
        串口的传输的服务层 请求这个页面就可以 将 要下发到 终端的指令 传输给 传给 服务
            有两个方案 :
                1. 基于 websocket 使用 dwebsocket 实现
                    2019.04.03  10:50 测试  ws 在除了本机 运行成功 阿里云 和树莓派 都失败 问题未知  使用 socket 重写 是 时间可能不够
                            暂时 否决 这个方案
                2. 使用 http 向 这个页面抓取 
                    在终端使用线程 定时在这里 请求数据
                    2019.04.03  11:00 使用 mqtt 在 服务里挂起接受的动作 mqtt 推送信息后自己
                        因为 mqtt 有阻塞机制所以 当有信息 的时候就来这里请求数据
                        这样可以节省处理开销 处理都在这个 后端上
        2019.04.03 13:32:
            具体动作:
                    创建 一个 工具方法 进行处理 要下发的东西 该方法作为 联动的 条件
                    当 处理到具体的信息 则 想 下发队列 里存放 下发指令 ,同时判断 如果下发指令 不为空 则 mqtt 推送 信息到 服务
                    
                    服务 接到 信息 就会来请求页面 ,页面 有情况 则 按照页面的 数据进行 解析 下发 控制 
                    
                    同时 该方法 读取 get 方法 为成功的时候才能 重新读取新的 数据 如果 重复 失败2 次 则 放弃这个 指令 重新 换一个和新的
                    
                    请求条件是 panid ,
                    #虽然这里 场景已经绑定了 这个网关 绑定了 协调器 但是 可以容纳 6 个 协调器  6个子网 还是 有必要划分
                    
            指令集:
                目前协调器 只添加 这些指令 不足的 一般 处理前面3 位 就可以确定具体动作 化间开发
                芯片开发 的时候在来 
                |数据动作|数据头|模块序列号|数据长度|数据内容|备注|
                |:--:|:--:|:--:|:--:|:--:|:--:|
                |执行器控制|0x02|0x12+0x34|0x02|0/1| 1是开 0是关|
                |数据采集控制|0x03|0x12+0x34|0x02|0/1| 1是开 0是关|
                |采集模块状态信息|0xf1|0x12+0x34|0x02|0x01|返回状态信息|
                |配置模块序列号|0xfc|0x12+0x34|0x03|0x1233  0x12|将 1234的机器设置成 panid 0x1233  信道12 |
                |重启控制|0xff|0x12+0x34|0x01|0xff| 重启模块序列的机器 0xffff 的时候 重启所有的机器|
                
                现在 定义几个指令 
                测试板 使用 cc2530 上的 led 灯 让其中一个当作是 执行器
                定义指令为 : 0x02 0x12 0x34 0x02 0x01 0x01 为 开灯的指令
                b"\x02\x12\x34\x02\x01\x01\r\n"
                
                同理 立即采集的指令:
                b"\x03\x12\x34\x02\x01\x01\r\n"
                
                配置 指令:
                b"\xfc\x12\x34\x03\x12\x33\x12\r\n"
                注意 这是 16 进制 信道号 是10 进制
                    如果直接 读取数字 输入框 记得 16 进制化
                    或者 用 下拉框限定 
                #别像当初 我朋友 帮我写 前端 配置工具的时候 直接读取 输入框的值 直接 转 字符串数字 
                    害我差点怀疑人生
                    
                重启系统的指令:
                    b"\xff\x12\x34\x01\xff\r\n"
                    直接调用 模块的reset 指令
                
                
                             
            
     工具方法:
     getDatafromList:
        将 list 的对象 信息 转化 字典 方便 方便使用 json 转发 到 getData 里
        

    retTag:
        用于处理 返回页面的 信息组装
        某人是返回 错误的字典 成功则会将 信息增加到 data 的 值里
        
    procData:
        数据处理的 方法
        
    Mqttsend:
        和 另一个 同一个 订阅里 这里发布一个 订阅 给 服务 服务收到信息就 会启动接受的处理.
    #     这个订阅 很少用到 这里仅仅是用于唤醒 服务 一处理 就处理到 空为止
        
        
    采集指令 集:
        因为时间有限 只适配 传感器信息 和 执行器 动作 其他的 只要 一次类推即可
        传感器信息: 第一位 标志 23 是 appid 4 是长度 长度 后面是 具体的各 单元信息
                没 3个 一组 每组 地一个是 标志 23 是具体值 直接存储到 信息表 即可 后期可以通过 数据库 进行 映射 成 具体的数据
                
                byte:b'\x01\x124\x09\x01\x02\x03\x02\x03\x04\x03\x04\x05\r\n'
                base64 str:ARI0CQECAwIDBAMEBQ0K
                注意:
                    1. 数据是大端 格式
                    2. 数值 在终端 进行放大 100 倍 因为 浮点处理 对 mcu 没那么好 所以 修改成整数
                        所以 这里需要 除以 100 才是 具体数值 
                        
    2019.04.03 20:08 
            基本完成 bug 在 运行这个后 运行 服务程序即可 联通整个系统
    2019.04.04 17:30
            问题:
            由于msgQueue 没有 添加 分类 所以 只支持 一个 终端 演示 
        
            如果 想要 添加 可以 对比 upload 的例子 将 sensoeList 划分多个 字典 
            下发 的 时候 更具 panid 进行 处理即可
"""
class Web(APIUnit):
    sensorList={}
    msgQueue=Queue()
    procThread=''
    mqttClient=''
    mqttHost="iotgeek.top"
    mqttPort=1883
    def __init__(self):
        self.mqttClient = mqtt.Client()
        self.mqttClient.connect(self.mqttHost, self.mqttPort, 60)

        self.mqttClient.subscribe("wujehy", 1)
        self.mqttClient.loop_start()
        return

    def Mqttsend(self,panid):
        """
        发送 一个 动作 仅此而已
        :return:
        """
        print("test mqtt")
        self.mqttClient.publish(topic="wujehy",payload=panid) # ok 就是 桥阶层的 控制判断指令
        return False
    def procData(self,panid):
        """
        这是一和处理现成
        当上传一个数据 后 就会启动一次
        实验是 当  panid 的温度 到达 36 的时候就 发送执行指令 开灯
        这种方式 是存在延时  因为 队列的清理需要时间 如果 信息不多的话 问题不大
        :param panid: 上传者的信息
        :return:
        """

        for x in  self.sensorList[panid]:
            # 自己配置　要发给自己panid 网络中的 的　指定对象
            # 如这里的 0x5011 是　我要发送的　位置　
            # 而我　接受的是　5011 的　第一个　数据　
            #
            # 一定是 4位数字的 16 进制的 panid
            toAppid=0x5001
            fromDevice=self.SensorNamebyAppidAndID(0x5011,1);
            obj=SensorData()
            obj.__dict__=self.sensorList[panid][x]
            if x == fromDevice:

                if obj.value>36:
                    # 开灯指令
                    # 适配 目标需要的指令
                    code=b"\x02\x12\x34\x02\x01\x01\r\n"
                    low=(toAppid&0xff00)>>8
                    high=toAppid&0xff
                    code=self.byteinData(code,1,low)
                    code=self.byteinData(code,2,high)
                    self.msgQueue.put(code,timeout=2)
                    print("open led",code)

                # if obj.SensorValue<35:
                #     code=b"\x02\x12\x34\x02\x00\x00\r\n"
                #     low=int(panid[0:2],16)
                #     high=int(panid[2:4],16)
                #     code=self.byteinData(code,1,low)
                #     code=self.byteinData(code,2,high)
                #     self.msgQueue.put(code,timeout=2)
                #     print("close Led",code)


        if not self.msgQueue.empty():
            print("mqttsend")
            self.Mqttsend(panid)


    def control(self,requset):
        try:
            if "panid" and 'data' in requset.GET:
                panid=requset.GET['panid']
                data =requset.GET['data']
                data=self.base64decode(data.encode())
                if self.msgQueue.full():
                    # 强制处理 同时等待处理2秒
                    self.Mqttsend(panid)
                self.msgQueue.put(data)
                self.Mqttsend(panid)
                return JsonResponse(self.retTag("msg in list",Type=True))
        except:
            # 任何错误都 出错
            return JsonResponse(self.retTag("error control"))

    def status(self,request):
        """

        :param request:
        :return:
        """
        if "panid" and "status" in request.GET:

            panid=request.GET['panid']
            status=request.GET['status']
            # 如果 panid 不存在 则直接 结束
            if panid not in self.sensorList:
                return JsonResponse(self.retTag("no panid in datalist"))

            if not self.msgQueue.empty():
                data=self.msgQueue.get(timeout=2)
                data={
                    "code":self.base64encode(data).decode(),
                    "len":self.msgQueue.qsize()
                }
                return JsonResponse(self.retTag(data,Type=True))
            else:
                return JsonResponse(self.retTag("msg is empty"))

        else:
            return JsonResponse(self.retTag("no param"))


    def getData(self,request):
        # temp=json.dumps()
        # print(temp)
        # temp=self.retTag(data=self.sensorList,Type=True)
        # print(temp)
        if self.sensorList.__len__() <1:
            return JsonResponse(self.retTag("no data"))
        else:
            return JsonResponse(self.retTag(data=self.sensorList,Type=True))

    def addSensorList(self,panid,obj,Type=False,Show=False):
        """
        将数据 存进 sensorlist
        # 如果 字典 没有 关键子 则创建 字典
        # 如果 存在 则 修改 更新

        :param panid: 上传 的panid
        :param obj:  数据对象
        :param Type:  类型 False 是 字典 格式 True 是 对象 格式
        :param Show  是否遍历 当前全部信息
        :return: True 是 成功  false 是 失败
        """
        status=False
        # 如果 字典 没有 关键子 则创建 字典
        # 如果 存在 则 修改 更新

        if panid in self.sensorList:
            pass
        else:
            self.sensorList[panid]={}

        if Type:
            self.sensorList[panid]["%s:%s" % (obj.Appid, obj.SensorName)]=obj
            status=True
        else:
            sensorID=self.SensorNamebyAppidAndID(obj.Appid,obj.SensorName)
            sensorType="id_%s:%s" % (hex(obj.Appid),obj.SensorName)
            print(sensorID)
            self.sensorList[panid][sensorID]={
                    "value": self.sensorValue(sensorType, obj.SensorValue),
                    "time": obj.lastdatetime,
                    "unit":self.sensorUnit(sensorType),
                    "cn_name":self.sensorCn_name(sensorType),
            }
            # self.__sensorList[panid]["%s:%s" % (hex(obj.Appid), obj.SensorName)] = obj.__dict__
            status=True
        #     因为这个方法是 只有字典才能直接 打印 所以限制一下

        # self.__sensorList[panid]=dicts
        if Show and status and Type is False:
            print(str(self.sensorList))

        return status

    def upload(self,request):
        """
        解析数据 的 格式
        byte:b'\x01\x124\x09\x01\x02\x03\x02\x03\x04\x03\x04\x05\r\n'
        base64 str:ARI0CQECAwIDBAMEBQ0K
        :param request:
        :return:
        """
        try:
            if "data" and "panid" in request.GET:
                # 原始数据处理
                data=request.GET['data']
                data=self.base64decode(data.encode())
                panid=request.GET["panid"]
                # 数据 具体解析
                if data[0] is 0x01 :
                    # 传感器 信息 的处理
                    print("sensor Data proc")
                    # 数据 载入 第四个 数据是长度 没三个 一组
                    len=data[3]
                    for x in range(int(len/3)):
                        # 数据是大端 格式 都将数据处理成 数字处理 后期 解析 或者 数据库处理
                        obj =SensorData()
                        obj.Appid= int.from_bytes(data[1:3],byteorder='big') #直接处理
                        obj.SensorName=data[4+3*x] # 后期 读取配置文件进行 修改
                        obj.SensorValue=int.from_bytes(data[4 + 3 * x + 1:4 + 3 * x + 3],byteorder='big')/100 #获取具体数据
                        obj.lastdatetime=self.TimeNow() #当前时间 例如 '2019-04-03T11:18:13'
                        print(self.addSensorList(panid,obj,Show=True))
                    self.procData(panid)
                    return  JsonResponse(self.retTag("upload ok",True))

            else:
                return JsonResponse(self.retTag("no parm"))
        except:
            return JsonResponse(self.retTag("data Type error"))

    def TimeNow(self):
        return str(time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime()))

    def byteinData(self,src,len,new):
        """
        指定 byte 的替换 因为 替换某个值 byte 不像数组 不能直接操作
        所以 遍历出来 在 修改 转化
        :param src: 源数据
        :param len: 位置
        :param new: 新值
        :return:
        """
        # 我的骚操作
        out=[(x==len and new or src[x]) for x in range(src.__len__())]
        # 等价
        # out=[x for x in src]
        # for x in range(src.__len__()):
        #     if x == len:
        #         out[x]=new

        return bytes(out)

    # 返回标志 组装
    def retTag(self,data=None,Type=False):
        """
        组装 返回信息 给浏览器

        :param data: 字典格式 的 数据
        :param Type:  选择 功能
        :return: 返回状态 的 字典
        """
        ret={"time":self.TimeNow()}

        if data is not  None:
            ret["data"]=data

        if Type:
            ret["status"]="success"
        else:

            ret["status"]="error"

        return  ret

    def base64encode(self,data):
        """
        输入的是byte
        将二进制格式转化成 字符
        :param data:
        :return:
        """
        return base64.encodebytes(data)

    def base64decode(self,data):
        """
        输入的是byte

        将字符转化 二进制
        :param data:
        :return:
        """
        return base64.decodebytes(data)

# @csrf_exempt
def page_not_found(request):
    return render_to_response('404.html')

# if __name__=="__main__":
#     Test=Web()
#
#     data='1234'
    # print(int()
    # print(Test.re.tTag("upload ok",True))
    # data=b'\x01\x124\x09\x01\x02\x03\x02\x43\x04\x03\x04\x05\r\n'
    # # code=Test.base64enARI0CQECAwJDBAMEBQ0K\ncode(data)
    # code=Test.byteinData(data,1,4)
    # print(code)



    # print(data)
    # panid =1234
    # if data[0] is 0x01:
    #     # 传感器 信息 的处理
    #     print("sensor Data proc")
    #     # 数据 载入 第四个 数据是长度 没三个 一组
    #     len = data[3]
    #     for x in range(int(len / 3)):
    #         # 数据是大端 格式 都将数据处理成 数字处理 后期 解析 或者 数据库处理
    #         obj = SensorData()
    #         obj.Appid = int.from_bytes(data[1:3], byteorder='big')  # 直接处理
    #         obj.SensorName = data[4 + 3 * x]  # 后期 读取配置文件进行 修改
    #         obj.SensorValue = int.from_bytes(data[4 + 3 * x + 1:4 + 3 * x + 3], byteorder='big') / 100  # 获取具体数据
    #         obj.lastdatetime = Test.TimeNow()  # 当前时间 例如 '2019-04-03T11:18:13'
    #         print(Test.addSensorList(panid, obj, Show=True))
    #
    # print("dsad")
    # print(json.dumps(Test.sensorList))

    # Test.msgQueue.put(data)
    #
    # print(Test.msgQueue.get())